﻿using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Reflection.Emit;
using System.Text;
using System.Threading.Tasks;

namespace VX.DBTransfer.Framework
{
    public class ConvertDataTableToEntity<T>
    {
        private static IDictionary<Type, Type> types = new Dictionary<Type, Type>();
        private static readonly MethodInfo getValueMethod = typeof(DataRow).GetMethod("get_Item", new Type[] { typeof(int) });
        private static readonly MethodInfo isDBNullMethod = typeof(DataRow).GetMethod("IsNull", new Type[] { typeof(int) });
        private delegate T Load(DataRow dataRecord);
        private Load handler;

        private ConvertDataTableToEntity() { }

        static ConvertDataTableToEntity()
        {
            types.Add(typeof(bool), typeof(Nullable<bool>));
            types.Add(typeof(byte), typeof(Nullable<byte>));
            types.Add(typeof(DateTime), typeof(Nullable<DateTime>));
            types.Add(typeof(decimal), typeof(Nullable<decimal>));
            types.Add(typeof(double), typeof(Nullable<double>));
            types.Add(typeof(float), typeof(Nullable<float>));
            types.Add(typeof(Guid), typeof(Nullable<Guid>));
            types.Add(typeof(Int16), typeof(Nullable<Int16>));
            types.Add(typeof(Int32), typeof(Nullable<Int32>));
            types.Add(typeof(Int64), typeof(Nullable<Int64>));
        }

        /// <summary>
        /// 采用动态委托生成对象
        /// </summary>
        /// <param name="dataRecord"></param>
        /// <returns></returns>
        public T Build(DataRow dataRecord)
        {
            return handler(dataRecord);
        }

        /// <summary>
        /// 初始化
        /// </summary>
        /// <param name="dataRecord"></param>
        /// <returns></returns>
        public static ConvertDataTableToEntity<T> CreateBuilder(DataRow dataRecord)
        {
            if (dataRecord == null) return null;
            ConvertDataTableToEntity<T> dynamicBuilder = new ConvertDataTableToEntity<T>();

            DynamicMethod method = new DynamicMethod("DynamicCreate", typeof(T), new Type[] { typeof(DataRow) }, typeof(T), true);
            ILGenerator generator = method.GetILGenerator();

            LocalBuilder result = generator.DeclareLocal(typeof(T));
            generator.Emit(OpCodes.Newobj, typeof(T).GetConstructor(Type.EmptyTypes));
            generator.Emit(OpCodes.Stloc, result);

            for (int i = 0; i < dataRecord.ItemArray.Length; i++)
            {
                PropertyInfo propertyInfo = typeof(T).GetProperty(dataRecord.Table.Columns[i].ColumnName);
                System.Reflection.Emit.Label endIfLabel = generator.DefineLabel();

                if (propertyInfo != null && propertyInfo.GetSetMethod() != null)
                {
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, isDBNullMethod);
                    generator.Emit(OpCodes.Brtrue, endIfLabel);

                    generator.Emit(OpCodes.Ldloc, result);
                    generator.Emit(OpCodes.Ldarg_0);
                    generator.Emit(OpCodes.Ldc_I4, i);
                    generator.Emit(OpCodes.Callvirt, getValueMethod);

                    bool isNullable = false;
                    if (propertyInfo.PropertyType.Name.ToLower().Contains("nullable"))
                        isNullable = true;
                    // Type _type = dataRecord.GetFieldType(i);
                    Type _type = dataRecord.Table.Columns[i].DataType;
                    if (isNullable)
                        generator.Emit(OpCodes.Unbox_Any, types[_type]);
                    else
                        generator.Emit(OpCodes.Unbox_Any, _type);

                    generator.Emit(OpCodes.Callvirt, propertyInfo.GetSetMethod());

                    generator.MarkLabel(endIfLabel);
                }
            }

            generator.Emit(OpCodes.Ldloc, result);
            generator.Emit(OpCodes.Ret);

            dynamicBuilder.handler = (Load)method.CreateDelegate(typeof(Load));
            return dynamicBuilder;
        }
    }
}
